/**
 * This tests whether slaveOk reads are properly routed through mongos in
 * an authenticated environment. This test also includes restarting the
 * entire set, then querying afterwards.
 *
 * This test involves a full restart of the replica set, so cannot be run with ephemeral storage
 * engines. When all nodes in a replica set are using an ephemeral storage engine, the set cannot
 * recover from a full restart. Once restarted, the nodes will have no knowledge of the replica set
 * config and will be unable to elect a primary.
 * @tags: [requires_persistence]
 */

/**
 * Checks if a query to the given collection will be routed to the secondary.
 *
 * @param {DBCollection} coll
 * @param {Object} query
 *
 * @return {boolean} true if query was routed to a secondary node.
 */
function doesRouteToSec(coll, query) {
    var explain = coll.find(query).explain();
    assert.eq("SINGLE_SHARD", explain.queryPlanner.winningPlan.stage);
    var serverInfo = explain.queryPlanner.winningPlan.shards[0].serverInfo;
    var conn = new Mongo(serverInfo.host + ":" + serverInfo.port.toString());
    var cmdRes = conn.getDB('admin').runCommand({isMaster: 1});

    jsTest.log('isMaster: ' + tojson(cmdRes));

    return cmdRes.secondary;
}

var rsOpts = {
    oplogSize: 50
};
var st = new ShardingTest(
    {keyFile: 'jstests/libs/key1', shards: 1, rs: rsOpts, other: {nopreallocj: 1}});

var mongos = st.s;
var replTest = st.rs0;
var testDB = mongos.getDB('AAAAA');
var coll = testDB.user;
var nodeCount = replTest.nodes.length;

/* Add an admin user to the replica member to simulate connecting from
 * remote location. This is because mongod allows unautheticated
 * connections to access the server from localhost connections if there
 * is no admin user.
 */
var adminDB = mongos.getDB('admin');
adminDB.createUser({user: 'user', pwd: 'password', roles: jsTest.adminUserRoles});
adminDB.auth('user', 'password');
var priAdminDB = replTest.getPrimary().getDB('admin');
priAdminDB.createUser({user: 'user', pwd: 'password', roles: jsTest.adminUserRoles},
                      {w: 3, wtimeout: 30000});

coll.drop();
coll.setSlaveOk(true);

/* Secondaries should be up here, but they can still be in RECOVERY
 * state, which will make the ReplicaSetMonitor mark them as
 * ok = false and not eligible for slaveOk queries.
 */
ReplSetTest.awaitRSClientHosts(mongos, replTest.getSecondaries(), {ok: true, secondary: true});

var bulk = coll.initializeUnorderedBulkOp();
for (var x = 0; x < 20; x++) {
    bulk.insert({v: x, k: 10});
}
assert.writeOK(bulk.execute({w: nodeCount}));

/* Although mongos never caches query results, try to do a different query
 * everytime just to be sure.
 */
var vToFind = 0;

jsTest.log('First query to SEC');
assert(doesRouteToSec(coll, {v: vToFind++}));

var SIG_TERM = 15;
replTest.stopSet(SIG_TERM, true, {auth: {user: 'user', pwd: 'password'}});

for (var n = 0; n < nodeCount; n++) {
    replTest.restart(n, rsOpts);
}

replTest.awaitSecondaryNodes();

coll.setSlaveOk(true);

/* replSetMonitor does not refresh the nodes information when getting secondaries.
 * A node that is previously labeled as secondary can now be a primary, so we
 * wait for the replSetMonitorWatcher thread to refresh the nodes information.
 */
ReplSetTest.awaitRSClientHosts(mongos, replTest.getSecondaries(), {ok: true, secondary: true});
//
// We also need to wait for the primary, it's possible that the mongos may think a node is a
// secondary but it actually changed to a primary before we send our final query.
//
ReplSetTest.awaitRSClientHosts(mongos, replTest.getPrimary(), {ok: true, ismaster: true});

// Recheck if we can still query secondaries after refreshing connections.
jsTest.log('Final query to SEC');
assert(doesRouteToSec(coll, {v: vToFind++}));

// Cleanup auth so Windows will be able to shutdown gracefully
priAdminDB = replTest.getPrimary().getDB('admin');
priAdminDB.auth('user', 'password');
priAdminDB.dropUser('user');

st.stop();
